package com.duguo.dynamicmvc.factory.processor.model;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtPrimitiveType;

import com.duguo.dynamicmvc.factory.ControllerBuilder;
import com.duguo.dynamicmvc.factory.ControllerFactory;
import com.duguo.dynamicmvc.factory.utils.BeanUtils;
import com.duguo.dynamicmvc.factory.utils.ChangeOnceBoolean;
import com.duguo.dynamicmvc.factory.utils.javassist.FieldUtils;
import com.duguo.dynamicmvc.factory.utils.javassist.MethodUtils;
import com.duguo.dynamicmvc.model.validator.exception.ValidationException;
import com.duguo.dynamicmvc.support.ModelAndViewImpl;

public class ModelBinderImpl implements ModelBinder{

	ModelValidationCreator _modelValidationCreator;
	ModelParameterCreator _modelParameterCreator;
	
    public void addModelBindCode(CtClass controllerCtClass,StringBuffer methodBody, CtClass modelCtClass,CtMethod controllerMethod,String returnText, ChangeOnceBoolean hasValidationException,ControllerBuilder controllerBuilder) throws Exception{
        methodBody.append(modelCtClass.getName() + " model=new "+modelCtClass.getName()+"();");
        String usedParam=getModelBindMethodParam(modelCtClass,controllerMethod);
        hasValidationException.setValue(_modelValidationCreator.hasValidationException(modelCtClass));
        hasValidationException.setValue(_modelValidationCreator.hasValidationException(controllerMethod));
        if(hasValidationException.isTrue()){
        	FieldUtils.setupField(controllerCtClass, controllerBuilder.getProcessorHolder().getControllerProcessor().getViewCtClass(), "validationExceptionView");
            methodBody.append("try{");
        }
        if(usedParam!=null)
        	methodBody.append("model.bind("+usedParam+");");
        if(hasValidationException.isTrue()){
        	methodBody.append(returnText);
            methodBody.append("}catch("+ValidationException.class.getName()+" ex){");
			methodBody.append("model.put(\"validationException\",ex);");
			methodBody.append("return new " + ModelAndViewImpl.class.getName() + "(model,"+"validationExceptionView"+");");
			methodBody.append("}");
        }else{
        	methodBody.append(returnText);
        }
        
    }


    public void createBindMethod(ControllerFactory controllerFactory,CtClass modelImplCtClass, Map<String, CtClass> getters, Map<String, CtClass> setters,CtMethod controllerMethod,Map<String,Object> staticObjects,ModelAnnotationReader modelAnnotationReader,ModelFieldCreator modelFieldCreator)throws Exception {
		for(String fieldName:setters.keySet()){
			if(!getters.containsKey(fieldName))
				continue;
			if(!modelAnnotationReader.isParameter(modelImplCtClass,fieldName,setters.get(fieldName))){
				getters.remove(fieldName);
			}
		}
		StringBuffer methodBody = new StringBuffer("");
		StringBuffer validationException = new StringBuffer("");
		Map<String,CtClass> usedParams=new HashMap<String,CtClass>();
		if(getters.size()>0){
			for(String fieldName:getters.keySet()){
				if(findParamFromResolver(controllerFactory,modelImplCtClass,fieldName,getters.get(fieldName),usedParams,methodBody,controllerMethod, staticObjects,validationException, modelAnnotationReader))
					continue;
				if(findParamInHandlerMethod(fieldName,getters.get(fieldName),usedParams,methodBody,controllerMethod))
					continue;
				findParamFromRequestParameter(controllerFactory,modelImplCtClass,fieldName,getters.get(fieldName),usedParams,methodBody,controllerMethod, staticObjects,validationException,modelAnnotationReader,modelFieldCreator);
			}
		}
		String usedParamStr = getParamString(usedParams);
		_modelValidationCreator.addThrowValidationException(validationException,methodBody);
		methodBody.insert(0, "{");
		if(validationException.length()>0)
			methodBody.insert(0, "throws "+ValidationException.class.getName());
		methodBody.insert(0, "public void bind("+usedParamStr+")");
        methodBody.append("}");
        MethodUtils.addMethod(modelImplCtClass, methodBody.toString());
	}




	String getModelBindMethodParam(CtClass modelImplCtClass, CtMethod controllerMethod)throws Exception {
		CtMethod bindMethod=MethodUtils.findMethod(modelImplCtClass, "bind");
		if(bindMethod!=null){
			StringBuffer usedParams = new StringBuffer("");
			for(CtClass paramType:bindMethod.getParameterTypes()){
				addBindParamFromControllerMethod(paramType,usedParams, controllerMethod);
			}
			return usedParams.toString();
		}else{
			return null;
		}
	}


	private void addBindParamFromControllerMethod(CtClass paramType,StringBuffer params, CtMethod controllerMethod) throws Exception {
		int position=1;
		for(CtClass currentParam:controllerMethod.getParameterTypes()){
			if(paramType==currentParam){
				if(params.length()!=0){
					params.append(",");
				}
				params.append("$"+position);
				return;
			}
			position++;
		}
	}



	boolean findParamFromResolver(ControllerFactory controllerFactory,CtClass modelImplCtClass, String fieldName, CtClass fieldType, Map<String, CtClass> usedParams, StringBuffer methodBody, CtMethod controllerMethod,Map<String,Object> staticObjects, StringBuffer validationException, ModelAnnotationReader modelAnnotationReader)throws Exception  {
		String paramName=_modelParameterCreator.getParameterNameInRequest(modelImplCtClass,fieldName,fieldType, modelAnnotationReader);
		Object resolver=null;
		try{
			resolver=_modelParameterCreator.getParameterResolver(controllerFactory,paramName);
		}catch(Exception ignore){}
		if(resolver!=null){
			CtClass resolverCtClass=ClassPool.getDefault().get(resolver.getClass().getName());
			CtMethod resolverMethod=MethodUtils.findMethod(resolverCtClass, "resolve");
			String resolverMethodParam=getResolverMethodParam(resolverMethod,controllerMethod,usedParams);
			
			boolean hasException=_modelValidationCreator.hasValidationException(resolverMethod);
			_modelValidationCreator.addValidationExceptionStart(methodBody, validationException,hasException);
			String resolverName=paramName + "Resolver";
			
			methodBody.append(BeanUtils.getSetterName(fieldName) + "(" + resolverName + ".resolve("+resolverMethodParam+"));");
			_modelValidationCreator.addValidationExceptionEnd(fieldName,methodBody, hasException);
			FieldUtils.setupStaticField(modelImplCtClass, resolverCtClass, resolverName,resolver);
			staticObjects.put(resolverName, resolver);
			return true;
		}
		return false;
	}


	private String getResolverMethodParam(CtMethod resolverMethod, CtMethod controllerMethod, Map<String, CtClass> usedParams)throws Exception {
		StringBuffer params = new StringBuffer("");
		for(CtClass paramType:resolverMethod.getParameterTypes()){
			String paramName=_modelParameterCreator.getNewParamName(paramType,usedParams,controllerMethod,false);
			if(params.length()>0)
				params.append(",");
			params.append(paramName);
		}
		return params.toString();
	}

	static String getParamString(Map<String, CtClass> usedParams) {
		String usedParamStr="";
		for(String currentParam:usedParams.keySet()){
			if(usedParamStr.length()>0)
				usedParamStr+=",";
			usedParamStr+=usedParams.get(currentParam).getName()+" "+currentParam;
		}
		return usedParamStr;
	}

	boolean findParamInHandlerMethod(String fieldName,CtClass fieldType,Map<String,CtClass> usedParams, StringBuffer methodBody, CtMethod controllerMethod) throws Exception {
		String paramName=_modelParameterCreator.getNewParamName(fieldType,usedParams,controllerMethod,true);
		if(paramName!=null){
			methodBody.append(BeanUtils.getSetterName(fieldName) + "("+paramName+");");
			return true;
		}
		else{
			return false;
		}
	}


	void findParamFromRequestParameter(ControllerFactory controllerFactory,CtClass modelImplCtClass, String fieldName, CtClass fieldType, Map<String, CtClass> usedParams, StringBuffer methodBody, CtMethod controllerMethod,Map<String,Object> staticObjects,StringBuffer validationException,ModelAnnotationReader modelAnnotationReader,ModelFieldCreator modelFieldCreator) throws Exception  {
		String requestParamName=_modelParameterCreator.getNewRequestParamName(usedParams,controllerMethod);
		List validators=modelAnnotationReader.getValidators(modelImplCtClass, fieldName, fieldType);
		boolean hasException=validators.size()>0;
		boolean isNumber=modelFieldCreator.isNumberField(fieldType);
		if(isNumber)
			hasException=true;
		_modelValidationCreator.addValidationExceptionStart(methodBody, validationException,hasException);
		String paramName=_modelParameterCreator.getParameterNameInRequest(modelImplCtClass,fieldName,fieldType, modelAnnotationReader);
      	if (fieldType.isPrimitive()) {
    		addPrimitiveFieldCode(controllerFactory,modelImplCtClass, fieldName, fieldType, usedParams, methodBody, controllerMethod, staticObjects, requestParamName, isNumber, paramName);
		} else if (fieldType==ClassPool.getDefault().get(String.class.getName())) {
			addStringFieldCode(fieldName, methodBody, requestParamName, paramName);
		} else if (fieldType.isArray()) {
			CtClass arrayType=fieldType.getComponentType();
			methodBody.append("{String[] tempStringValue="+requestParamName+".getParameterValues(\""+paramName+"\");");
			if (arrayType.isPrimitive()) {
	    		addPrimitiveArrayFieldCode(controllerFactory,modelImplCtClass, fieldName, usedParams, methodBody, controllerMethod, staticObjects, isNumber, arrayType);
			} else if (arrayType==ClassPool.getDefault().get(String.class.getName())) {
				addStringArrayFeildCode(fieldName, methodBody);
			}else{
				throw new RuntimeException("Cannot create bind for "+modelImplCtClass.getName()+"."+fieldName+" ["+fieldType.getName()+"]");
			}
		}else{
			throw new RuntimeException("Cannot create bind for "+modelImplCtClass.getName()+"."+fieldName+" ["+fieldType.getName()+"]");
		} 
      	_modelValidationCreator.addNoneRequiredValidators(controllerFactory,fieldName,modelImplCtClass, usedParams, methodBody, controllerMethod,staticObjects,validators, modelAnnotationReader);
		methodBody.append("}");
		addDefaultValueOrRequiredValidator(controllerFactory,fieldName,fieldType,modelImplCtClass, usedParams, methodBody, controllerMethod,staticObjects,validators,modelAnnotationReader);
		methodBody.append("}");
		_modelValidationCreator.addValidationExceptionEnd(fieldName,methodBody, hasException);
	}


	private void addPrimitiveArrayFieldCode(ControllerFactory controllerFactory,CtClass modelImplCtClass, String fieldName, Map<String, CtClass> usedParams, StringBuffer methodBody, CtMethod controllerMethod, Map<String, Object> staticObjects, boolean isNumber, CtClass arrayType) throws Exception {
		methodBody.append("if(tempStringValue!=null){");
		CtPrimitiveType fieldPrimitiveType = (CtPrimitiveType) arrayType;
		if(isNumber){
			methodBody.append("try{");
		}
		methodBody.append(BeanUtils.getSetterName(fieldName) + "(new "+ arrayType.getName() + "[tempStringValue.length]);");
		methodBody.append("if(tempStringValue.length>0){for(int i=0;i<tempStringValue.length;i++){");
		methodBody.append(" this." + fieldName + "[i]="+ fieldPrimitiveType.getWrapperName() + ".valueOf(tempStringValue[i].trim())."+ fieldPrimitiveType.getGetMethodName() + "();");
		methodBody.append("}}");
		_modelValidationCreator.addNumberFomratValidator(controllerFactory,fieldName, methodBody, isNumber, fieldPrimitiveType,modelImplCtClass,usedParams, controllerMethod,staticObjects);
	}


	private void addStringArrayFeildCode(String fieldName, StringBuffer methodBody) {
		methodBody.append("if(tempStringValue!=null){");
		methodBody.append(BeanUtils.getSetterName(fieldName) + "(tempStringValue);");
	}


	private void addStringFieldCode(String fieldName, StringBuffer methodBody, String requestParamName, String paramName) {
		methodBody.append("{String tempStringValue="+requestParamName+".getParameter(\""+paramName+"\");");
		methodBody.append("if(tempStringValue!=null && tempStringValue.length()>0){");
		methodBody.append(BeanUtils.getSetterName(fieldName) + "(tempStringValue);");
	}


	private void addPrimitiveFieldCode(ControllerFactory controllerFactory,CtClass modelImplCtClass, String fieldName, CtClass fieldType, Map<String, CtClass> usedParams, StringBuffer methodBody, CtMethod controllerMethod, Map<String, Object> staticObjects, String requestParamName, boolean isNumber, String paramName) throws Exception {
		methodBody.append("{String tempStringValue="+requestParamName+".getParameter(\""+paramName+"\");");
		methodBody.append("if(tempStringValue!=null && tempStringValue.trim().length()>0){");
		CtPrimitiveType fieldPrimitiveType = (CtPrimitiveType) fieldType;
		if(isNumber){
			methodBody.append("try{");
		}
		methodBody.append(BeanUtils.getSetterName(fieldName) + "("+ fieldPrimitiveType.getWrapperName() + ".valueOf(tempStringValue.trim())."+ fieldPrimitiveType.getGetMethodName() + "());");
		_modelValidationCreator.addNumberFomratValidator(controllerFactory,fieldName, methodBody, isNumber, fieldPrimitiveType,modelImplCtClass,usedParams, controllerMethod,staticObjects);
	}



	private void addDefaultValueOrRequiredValidator(ControllerFactory controllerFactory,String fieldName, CtClass fieldType,CtClass modelImplCtClass, Map<String, CtClass> usedParams, StringBuffer methodBody, CtMethod controllerMethod,Map<String,Object> staticObjects, List validators,ModelAnnotationReader modelAnnotationReader) throws Exception {
		String defaultValue= modelAnnotationReader.getDefaultValue(modelImplCtClass,fieldName,fieldType,true);
		if(defaultValue!=null){
	        methodBody.append("else{");
	        if (fieldType.isPrimitive()) {
	    		methodBody.append(BeanUtils.getSetterName(fieldName) + "("+ defaultValue + ");");
			} else if (fieldType==ClassPool.getDefault().get(String.class.getName())) {
	    		methodBody.append(BeanUtils.getSetterName(fieldName) + "(\""+ defaultValue + "\");");
			}
			methodBody.append("}");
		}else{
			_modelValidationCreator.addRequiredValidator(controllerFactory,fieldName,modelImplCtClass, usedParams, methodBody, controllerMethod,staticObjects,validators, modelAnnotationReader);
		}
	}


	public ModelValidationCreator getModelValidationCreator() {
		return _modelValidationCreator;
	}


	public void setModelValidationCreator(
			ModelValidationCreator modelValidationCreator) {
		_modelValidationCreator = modelValidationCreator;
	}


	public ModelParameterCreator getModelParameterCreator() {
		return _modelParameterCreator;
	}


	public void setModelParameterCreator(ModelParameterCreator modelParameterCreator) {
		_modelParameterCreator = modelParameterCreator;
	}





}
